using System.Runtime.InteropServices;
using Pinwheel.LSerialize;
using Pinwheel.Service;
using Pinwheel.Service.Protocol;

namespace Pinwheel.Kernel
{
    [LSerializable]
    [StructLayout(LayoutKind.Explicit, Size = 8)]
    public partial struct PID
    {
        public const int LOCAL = 0;
        
        private static int HID = 0;
        private static int LID = 0;
        
        [LSerializeInclude] [FieldOffset(0)] private long m_Value;
        [LSerializeIgnore] [FieldOffset(0)] public int Remote;
        [LSerializeIgnore] [FieldOffset(4)] public int Service;

        private static int GenRemote()
        {
            return HID switch
            {
                -1 => Interlocked.Add(ref HID, 2),
                _ => Interlocked.Increment(ref LID)
            };
        }
        
        private static int GenService()
        {
            return LID switch
            {
                -1 => Interlocked.Add(ref LID, 3),
                0 => Interlocked.Add(ref LID, 2),
                _ => Interlocked.Increment(ref LID)
            };
        }

        [LSerializeIgnore] public bool IsLocal => Remote == LOCAL;
        [LSerializeIgnore] public bool IsValid => this != Invalid && this != System;

        public static readonly PID Invalid = default;
        public static readonly PID Zero = Create(0, 0);
        public static readonly PID System = Create(LOCAL, 1);

        public static PID Create()
        {
            var remote = GenRemote();
            var service = GenService();
            var pid = Create(remote, service);
            return pid;
        }
        
        public static PID Create(int remote)
        {
            var service = GenService();
            var pid = Create(remote, service);
            return pid; 
        }
        
        public static PID Create(int remote, int service)
        {
            return new PID()
            {
                Remote = remote,
                Service = service
            };
        }

        // internal IService? CurrentRef { get; private set; }

        // internal PID(string address, IService service, bool isIPv6 = false) : this(address, isIPv6)
        // {
        //     CurrentRef = service;
        // }

        internal IService Ref(IServiceManager manager)
        {
            // if (null != CurrentRef)
            // {
            //     if (CurrentRef is IServiceInstance { Status: ServiceStatus.Stopped })
            //         CurrentRef = null;
            //     if (CurrentRef != null) 
            //         return CurrentRef;
            // }
            //
            // CurrentRef = manager.GetService(this);
            // return CurrentRef;
            return manager.GetService(this);
        }
        
        public void Send(IServiceManager manager, PID source, string service, string method, MessageType type, IMessage message)
        {
            var serviceMessage = new ServiceMessage();
            serviceMessage.SetType(type);
            serviceMessage.SetRoute(source, this);
            serviceMessage.SetMethod(method);
            serviceMessage.SetMessage(message);
            Send(manager, service, serviceMessage);
        }
        
        public async Task<IMessage> Call(IServiceManager manager, PID source, string service, MessageType type, byte[] buffer, int timeout = 5000)
        {
            var serviceMessage = LSerializer.Deserialize<ServiceMessage>(buffer);
            
            Assert.Check(serviceMessage != null, $"[{Ref(manager).Alias}]Send buffer deserialize failed: {buffer}");
            Assert.Check(source == System || serviceMessage.Source == source, $"[{Ref(manager).Alias}]Send buffer deserialize source not match: {serviceMessage.Source} != {source}");
            Assert.Check(serviceMessage.Type == type, $"[{Ref(manager).Alias}]Send buffer deserialize type not match: {serviceMessage.Type} != {type}");
            
            if (serviceMessage.Source != source) serviceMessage.SetRoute(source, serviceMessage.Destination);
            if (serviceMessage.Type != type) serviceMessage.SetType(type);
            
            var tcs = source != Invalid && source != System && source.Ref(manager) is IMessageHandler handler
                ? handler.InvokeMessageHandler(MessageHandlerType.Send, ref serviceMessage)
                : Global.Rpc.HandleSend(ref serviceMessage);
            
            Send(manager, service, serviceMessage);
            
            var timeoutTask = Task.Delay(timeout);
            var completedTask = await Task.WhenAny(tcs.Task, timeoutTask);
            
            
            if (completedTask == timeoutTask)
            {
                tcs.TrySetException(new TimeoutException("Call operation timed out."));
                throw new TimeoutException("Call operation timed out.");
            }
            
            return await tcs.Task;
        }
        
        public async Task<IMessage> Call(IServiceManager manager, PID source, string service, string method, MessageType type, IMessage message, int timeout = 5000)
        {
            var serviceMessage = new ServiceMessage();
            serviceMessage.SetType(type);
            serviceMessage.SetRoute(source, this);
            serviceMessage.SetMethod(method);
            serviceMessage.SetMessage(message);
            
            return await Call(manager, service, serviceMessage, timeout);
        }

        public async Task<IMessage> Call(IServiceManager manager, string service, ServiceMessage message, int timeout = 5000)
        {
            var tcs = message.Source != Invalid && message.Source != System && message.Source.Ref(manager) is IMessageHandler handler
                ? handler.InvokeMessageHandler(MessageHandlerType.Send, ref message)
                : Global.Rpc.HandleSend(ref message);

            Send(manager, service, message);
            
            var timeoutTask = Task.Delay(timeout);
            var completedTask = await Task.WhenAny(tcs.Task, timeoutTask);
            
            if (completedTask == timeoutTask)
            {
                tcs.TrySetException(new TimeoutException("Call operation timed out."));
                throw new TimeoutException("Call operation timed out.");
            }
            
            return await tcs.Task; 
        }
        
        public void Send(IServiceManager manager, PID source, MessageType type, ServiceMessage message)
        {
            if (message.Type != type) message.SetType(type);
            Send(manager, source, message);
        }
        
        public void Send(IServiceManager manager, PID source, ServiceMessage message)
        {
            if (message.Source != source) message.SetRoute(source, message.Destination);
            Send(manager, null!, message);
        }
        
        public void Send(IServiceManager manager, PID source, ref NetPacket packet)
        {
            if (IsLocal)
            {
                ServiceMessage message = LSerializer.Deserialize<ServiceMessage>(packet.Data);
                if (!source.IsLocal) message.SetRoute(source, this);
                
                Assert.Check(message != default, $"[{Ref(manager).Alias}]Send buffer deserialize failed: {packet.Data}");
                Assert.Check(source == System || message.Source == source, $"[{Ref(manager).Alias}]Send buffer deserialize source not match: {message.Source} != {source}");
                Assert.Check(message.Type == (MessageType)packet.Type, $"[{Ref(manager).Alias}]Send buffer deserialize type not match: {message.Type} != {(MessageType)packet.Type}");
                
                if (message.Source != source) message.SetRoute(source, message.Destination);
                if (message.Type != (MessageType)packet.Type) message.SetType((MessageType)packet.Type);
                
                Ref(manager).Post(message); 
            }
            else
            {
                if (Global.ClientConnectionMap.TryGetValue(this, out var conn))
                {
                    conn.SendAsync(packet);
                }
                else if (Global.NodeConnectionMap.TryGetValue(this, out var node))
                {
                    node.SendAsync(packet);
                }
                Assert.Check(false, $"[{Ref(manager).Alias}]Send remote connection not found: {this}");
            }
        }

        public void Send(IServiceManager manager, string service, ServiceMessage message)
        {
            if (IsLocal)
            {
                Ref(manager).Post(message);
            }
            else
            {
                if (Global.ClientConnectionMap.TryGetValue(this, out var client))
                {
                    client.SendAsync(service, message);
                }
                else if (Global.NodeConnectionMap.TryGetValue(this, out var node))
                {
                    Assert.Check(!string.IsNullOrEmpty(service), $"Send remote service not found: {this}");
                    node.SendAsync(service, message);
                }
                else
                {
                    Assert.Check(false, $"Send remote connection not found: {this}");
                }
            }
        }
        
    }
}